/**
 * Copyright (c) Tapas Mobile.  All Rights Reserved.
 *
 * @author DaiHui 
 * @version 1.0
 */

package com.dotools.preferences;

import java.lang.reflect.Method;
import java.util.HashMap;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import com.dotools.base.CommonConstants;
import com.dotools.debug.LOG;
import com.dotools.reflection.ReflectionUtils;
import com.dotools.thread.ThreadPool;
import com.dotools.utils.Utilities;

public class AppPreferences {
    private static final boolean LOGV = CommonConstants.IS_DEBUG && false;

    // never change this
    final static String PREFERENCE_NAME = CommonConstants.PROCESS_NAME + "_pref";

    static SharedPreferences sSP = null;

    static final Method methodApply;

    static {
        Class<?> clsEditor = ReflectionUtils.loadClassNoThrow(ClassLoader.getSystemClassLoader(),
                "android.content.SharedPreferences$Editor");

        if (clsEditor != null)
            methodApply = ReflectionUtils.findMethodNoThrow(clsEditor, "apply", new Class<?>[] {});
        else
            methodApply = null;
    }

    public static void init() {
        synchronized (AppPreferences.class) {
            sSP = Utilities.getApplicationContext().getSharedPreferences(PREFERENCE_NAME,
                    Context.MODE_PRIVATE);
        }
    }

    public static void finish() {
        sSP = null;
    }

    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    private static void asyncCommit(final SharedPreferences.Editor editor) {
        if (editor != null) {
            if (methodApply != null) {
                editor.apply();
            } else {
                ThreadPool.runOnPool(new Runnable() {
                    @Override
                    public void run() {
                        editor.commit();
                    }
                });
            }
        }
    }

    // --------------------------------------------------------------------------------
    // handle string
    // --------------------------------------------------------------------------------
    public static String getString(String key, String defValue) {
        Object objec = map.get(key);
        if(objec != null) {
            return objec.toString();
        }
        synchronized (AppPreferences.class) {
            String v = sSP.getString(key, defValue);
            map.put(key, v);
            return v;
        }
    }

    public static void putString(String key, String value) {
        putString(key, value, true);
    }

    public static void putString(String key, String value, boolean bCommitNow) {
        if (LOGV) {
            if (value != null && value.length() > 256) {
                LOG.logE(key + " should not be stored with AppPreference, it is too long. length=" + value.length());
                return;
            }
        }

        if (hasKey(key)) {
            String old = getString(key, null);
            if (old != null && old.equals(value)) {
                if (LOGV) LOG.logI("value not changed: key=" + key + ", value=" + old);
                return;
            }
        }

        map.put(key, value);
        synchronized (AppPreferences.class) {
            SharedPreferences.Editor editor = sSP.edit();
            editor.putString(key, value);

            if (bCommitNow) {
                asyncCommit(editor);
            }
        }
    }

    public static void flush(){
        synchronized (AppPreferences.class) {
            SharedPreferences.Editor editor = sSP.edit();
            asyncCommit(editor);
        }
    }
    // --------------------------------------------------------------------------------
    // handle integer
    // --------------------------------------------------------------------------------
    public static int getInt(String key, int defValue) {
        Object objec = map.get(key);
        if(objec != null) {
            return (Integer) objec;
        }
        synchronized (AppPreferences.class) {
            int i = sSP.getInt(key, defValue);
            map.put(key, i);
            return i;
        }
    }

    public static void putInt(String key, int value) {
        putInt(key, value, true);
    }

    public static void putInt(String key, int value, boolean bCommitNow) {
        if (hasKey(key)) {
            int old = getInt(key, Integer.MIN_VALUE);
            if (old == value && old != Integer.MIN_VALUE) {
                if (LOGV) LOG.logI("value not changed: key=" + key + ", value=" + old);
                return;
            }
        }
        map.put(key, value);
        synchronized (AppPreferences.class) {
            SharedPreferences.Editor editor = sSP.edit();
            editor.putInt(key, value);

            if (bCommitNow) {
                asyncCommit(editor);
            }
        }
    }

    // --------------------------------------------------------------------------------
    // handle long
    // --------------------------------------------------------------------------------
    public static long getLong(String key, long defValue) {
        Object objec = map.get(key);
        if(objec != null) {
            return (Long) objec;
        }
        synchronized (AppPreferences.class) {
            return sSP.getLong(key, defValue);
        }
    }

    public static void putLong(String key, long value) {
        putLong(key, value, true);
    }

    public static void putLong(String key, long value, boolean bCommitNow) {
        if (hasKey(key)) {
            long old = getLong(key, Long.MIN_VALUE);
            if (old == value && old != Long.MIN_VALUE) {
                if (LOGV) LOG.logI("value not changed: key=" + key + ", value=" + old);
                return;
            }
        }
        map.put(key, value);
        synchronized (AppPreferences.class) {
            SharedPreferences.Editor editor = sSP.edit();
            editor.putLong(key, value);

            if (bCommitNow) {
                asyncCommit(editor);
            }
        }
    }

    // --------------------------------------------------------------------------------
    // handle boolean
    // --------------------------------------------------------------------------------
    public static boolean getBoolean(String key, boolean defValue) {
        Object objec = map.get(key);
        if(objec != null) {
            return (Boolean) objec;
        }
        synchronized (AppPreferences.class) {
            return sSP.getBoolean(key, defValue);
        }
    }

    public static void putBoolean(String key, boolean value) {
        putBoolean(key, value, true);
    }

    public static void putBoolean(String key, boolean value, boolean bCommitNow) {
        if (hasKey(key)) {
            boolean old = getBoolean(key, false); // must return valid true / false, so do not check old == false
            if (old == value) {
                if (LOGV) LOG.logI("value not changed: key=" + key + ", value=" + old);
                return;
            }
        }


        map.put(key, value);
        synchronized (AppPreferences.class) {
            SharedPreferences.Editor editor = sSP.edit();
            editor.putBoolean(key, value);

            if (bCommitNow) {
                asyncCommit(editor);
            }
        }
    }

    // --------------------------------------------------------------------------------
    // handle float
    // --------------------------------------------------------------------------------
    public static float getFloat(String key, float defValue) {
        Object objec = map.get(key);
        if(objec != null) {
            return (Float) objec;
        }
        synchronized (AppPreferences.class) {
            return sSP.getFloat(key, defValue);
        }
    }

    public static void putFloat(String key, float value) {
        putFloat(key, value, true);
    }

    static void putFloat(String key, float value, boolean bCommitNow) {
        if (hasKey(key)) {
            float old = getFloat(key, Float.MAX_VALUE);
            if (old == value && old != Float.MAX_VALUE) {
                if (LOGV) LOG.logI("value not changed: key=" + key + ", value=" + old);
                return;
            }
        }


        map.put(key, value);
        synchronized (AppPreferences.class) {
            SharedPreferences.Editor editor = sSP.edit();
            editor.putFloat(key, value);

            if (bCommitNow) {
                asyncCommit(editor);
            }
        }
    }

    // --------------------------------------------------------------------------------
    // remove
    // --------------------------------------------------------------------------------
    public static void remove(String key) {
        remove(key, true);
    }

    public static void remove(String key, boolean bCommitNow) {
        if (!hasKey(key)) {
            return;
        }
        map.remove(key);
        synchronized (AppPreferences.class) {
            SharedPreferences.Editor editor = sSP.edit();
            editor.remove(key);

            if (bCommitNow) {
                asyncCommit(editor);
            }
        }
    }

    public static void remove(String[] keys) {
        if (keys == null || keys.length == 0) {
            return;
        }
        for (String string : keys) {
            map.remove(string);
        }
        boolean bCommitNow = false;
        synchronized (AppPreferences.class) {
            SharedPreferences.Editor editor = sSP.edit();
            for (int i = 0; i < keys.length; i++) {
                if (sSP.contains(keys[i])) {
                    bCommitNow = true;
                    editor.remove(keys[i]);
                }
            }
            if (bCommitNow) {
                asyncCommit(editor);
            }
        }
    }

    // --------------------------------------------------------------------------------
    // check if key present
    // --------------------------------------------------------------------------------
    static boolean hasKey(String key) {
        if(map.containsKey(key)) {
            return true;
        }
        synchronized (AppPreferences.class) {
            return sSP.contains(key);
        }
    }

    private static HashMap<String, Object> map = new HashMap<String, Object>();
}
